/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */

//
// Javascript ZLib
// By Thomas Down 2010-2011
//
// Based very heavily on portions of jzlib (by ymnk@jcraft.com), who in
// turn credits Jean-loup Gailly and Mark Adler for the original zlib code.
//
// inflate.js: ZLib inflate code
//

//
// Shared constants
//

import 'dart:typed_data';
import 'constants.dart';

//
// ZStream.java
//

class ZStream {
  String msg;
  Inflate istate;
  var total_in;
  var total_out;
  var next_in;
  var avail_in;

  var next_in_index;

  var adler;

  var next_out_index;

  int avail_out;
  Uint8List next_out;

  inflateInit(w, nowrap) {
    if (!w) {
      w = DEF_WBITS;
    }
    if (nowrap) {
      nowrap = false;
    }
    this.istate = Inflate();
    return this.istate.inflateInit(this, nowrap ? -w : w);
  }

  inflate(f) {
    if (this.istate == null) return Z_STREAM_ERROR;
    return this.istate.inflate(this, f);
  }

  inflateEnd() {
    if (this.istate == null) return Z_STREAM_ERROR;
    var ret = istate.inflateEnd(this);
    this.istate = null;
    return ret;
  }

  inflateSync() {
    // if(istate == null) return Z_STREAM_ERROR;
    return istate.inflateSync(this);
  }

  inflateSetDictionary(dictionary, dictLength) {
    // if(istate == null) return Z_STREAM_ERROR;
    return istate.inflateSetDictionary(this, dictionary, dictLength);
  }
}

//
// Inflate.java
//

class Inflate {
  List was = [0];
  InfBlocks blocks;
  var wbits;
  var nowrap;
  int mode;

  var need;

  var method;

  int marker;

  inflateReset(ZStream z) {
    if (z == null || z.istate == null) return Z_STREAM_ERROR;

    z.total_in = z.total_out = 0;
    z.msg = null;
    z.istate.mode = z.istate.nowrap != 0 ? BLOCKS : METHOD;
    z.istate.blocks.reset(z, null);
    return Z_OK;
  }

  inflateEnd(z) {
    if (this.blocks != null) this.blocks.free(z);
    this.blocks = null;
    return Z_OK;
  }

  inflateInit(ZStream z, w) {
    z.msg = null;
    this.blocks = null;

    // handle undocumented nowrap option (no zlib header or check)
    var nowrap = 0;
    if (w < 0) {
      w = -w;
      nowrap = 1;
    }

    // set window size
    if (w < 8 || w > 15) {
      this.inflateEnd(z);
      return Z_STREAM_ERROR;
    }
    this.wbits = w;

    z.istate.blocks = InfBlocks(z, z.istate.nowrap != 0 ? null : this, 1 << w);

    // reset state
    this.inflateReset(z);
    return Z_OK;
  }

  inflate(ZStream z, f) {
    var r, b;

    if (z == null || z.istate == null || z.next_in == null) return Z_STREAM_ERROR;
    f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK;
    r = Z_BUF_ERROR;
    while (true) {
      switch (z.istate.mode) {
        case METHOD:
          if (z.avail_in == 0) return r;
          r = f;
          z.avail_in--;
          z.total_in++;
          if (((z.istate.method = z.next_in[z.next_in_index++]) & 0xf) != Z_DEFLATED) {
            z.istate.mode = BAD;
            z.msg = "unknown compression method";
            z.istate.marker = 5; // can't try inflateSync
            break;
          }
          if ((z.istate.method >> 4) + 8 > z.istate.wbits) {
            z.istate.mode = BAD;
            z.msg = "invalid window size";
            z.istate.marker = 5; // can't try inflateSync
            break;
          }
          z.istate.mode = FLAG;
          continue;
        case FLAG:
          if (z.avail_in == 0) return r;
          r = f;

          z.avail_in--;
          z.total_in++;
          b = (z.next_in[z.next_in_index++]) & 0xff;

          if ((((z.istate.method << 8) + b) % 31) != 0) {
            z.istate.mode = BAD;
            z.msg = "incorrect header check";
            z.istate.marker = 5; // can't try inflateSync
            break;
          }

          if ((b & PRESET_DICT) == 0) {
            z.istate.mode = BLOCKS;
            break;
          }
          z.istate.mode = DICT4;
          continue;
        case DICT4:
          if (z.avail_in == 0) return r;
          r = f;
          z.avail_in--;
          z.total_in++;
          z.istate.need = ((z.next_in[z.next_in_index++] & 0xff) << 24) & 0xff000000;
          z.istate.mode = DICT3;
          continue;
        case DICT3:
          if (z.avail_in == 0) return r;
          r = f;
          z.avail_in--;
          z.total_in++;
          z.istate.need += ((z.next_in[z.next_in_index++] & 0xff) << 16) & 0xff0000;
          z.istate.mode = DICT2;
          continue;
        case DICT2:
          if (z.avail_in == 0) return r;
          r = f;
          z.avail_in--;
          z.total_in++;
          z.istate.need += ((z.next_in[z.next_in_index++] & 0xff) << 8) & 0xff00;
          z.istate.mode = DICT1;
          continue;
        case DICT1:
          if (z.avail_in == 0) return r;
          r = f;
          z.avail_in--;
          z.total_in++;
          z.istate.need += (z.next_in[z.next_in_index++] & 0xff);
          z.adler = z.istate.need;
          z.istate.mode = DICT0;
          return Z_NEED_DICT;
        case DICT0:
          z.istate.mode = BAD;
          z.msg = "need dictionary";
          z.istate.marker = 0; // can try inflateSync
          return Z_STREAM_ERROR;
        case BLOCKS:
          r = z.istate.blocks.proc(z, r);
          if (r == Z_DATA_ERROR) {
            z.istate.mode = BAD;
            z.istate.marker = 0; // can try inflateSync
            break;
          }
          if (r == Z_OK) {
            r = f;
          }
          if (r != Z_STREAM_END) {
            return r;
          }
          r = f;
          z.istate.blocks.reset(z, z.istate.was);
          if (z.istate.nowrap != 0) {
            z.istate.mode = DONE;
            break;
          }
          z.istate.mode = CHECK4;
          continue;
        case CHECK4:
          if (z.avail_in == 0) return r;
          r = f;

          z.avail_in--;
          z.total_in++;
          z.istate.need = ((z.next_in[z.next_in_index++] & 0xff) << 24) & 0xff000000;
          z.istate.mode = CHECK3;
          continue;
        case CHECK3:
          if (z.avail_in == 0) return r;
          r = f;

          z.avail_in--;
          z.total_in++;
          z.istate.need += ((z.next_in[z.next_in_index++] & 0xff) << 16) & 0xff0000;
          z.istate.mode = CHECK2;
          continue;
        case CHECK2:
          if (z.avail_in == 0) return r;
          r = f;

          z.avail_in--;
          z.total_in++;
          z.istate.need += ((z.next_in[z.next_in_index++] & 0xff) << 8) & 0xff00;
          z.istate.mode = CHECK1;
          continue;
        case CHECK1:
          if (z.avail_in == 0) return r;
          r = f;

          z.avail_in--;
          z.total_in++;
          z.istate.need += (z.next_in[z.next_in_index++] & 0xff);

          if (((z.istate.was[0])) != ((z.istate.need))) {
            z.istate.mode = BAD;
            z.msg = "incorrect data check";
            z.istate.marker = 5; // can't try inflateSync
            break;
          }
          z.istate.mode = DONE;
          continue;
        case DONE:
          return Z_STREAM_END;
        case BAD:
          return Z_DATA_ERROR;
        default:
          return Z_STREAM_ERROR;
      }
    }
  }

  inflateSetDictionary(ZStream z, dictionary, dictLength) {
    var index = 0;
    var length = dictLength;
    if (z == null || z.istate == null || z.istate.mode != DICT0) return Z_STREAM_ERROR;

    if (adler32(1, dictionary, 0, dictLength) != z.adler) {
      return Z_DATA_ERROR;
    }

    z.adler = adler32(0, null, 0, 0);

    if (length >= (1 << z.istate.wbits)) {
      length = (1 << z.istate.wbits) - 1;
      index = dictLength - length;
    }
    z.istate.blocks.set_dictionary(dictionary, index, length);
    z.istate.mode = BLOCKS;
    return Z_OK;
  }

//  static private byte[] mark = {(byte)0, (byte)0, (byte)0xff, (byte)0xff};
  var mark = [0, 0, 255, 255];

  inflateSync(ZStream z) {
    var n; // number of bytes to look at
    var p; // pointer to bytes
    var m; // number of marker bytes found in a row
    var r, w; // temporaries to save total_in and total_out

    // set up
    if (z == null || z.istate == null) return Z_STREAM_ERROR;
    if (z.istate.mode != BAD) {
      z.istate.mode = BAD;
      z.istate.marker = 0;
    }
    if ((n = z.avail_in) == 0) return Z_BUF_ERROR;
    p = z.next_in_index;
    m = z.istate.marker;

    // search
    while (n != 0 && m < 4) {
      if (z.next_in[p] == mark[m]) {
        m++;
      } else if (z.next_in[p] != 0) {
        m = 0;
      } else {
        m = 4 - m;
      }
      p++;
      n--;
    }

    // restore
    z.total_in += p - z.next_in_index;
    z.next_in_index = p;
    z.avail_in = n;
    z.istate.marker = m;

    // return no joy or set up to restart on a new block
    if (m != 4) {
      return Z_DATA_ERROR;
    }
    r = z.total_in;
    w = z.total_out;
    this.inflateReset(z);
    z.total_in = r;
    z.total_out = w;
    z.istate.mode = BLOCKS;
    return Z_OK;
  }

// Returns true if inflate is currently at the end of a block generated
// by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
// implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
// but removes the length bytes of the resulting empty stored block. When
// decompressing, PPP checks that at the end of input packet, inflate is
// waiting for these length bytes.
  inflateSyncPoint(ZStream z) {
    if (z == null || z.istate == null || z.istate.blocks == null) return Z_STREAM_ERROR;
    return z.istate.blocks.sync_point();
  }
}

//
// InfBlocks.java
//

const INFBLOCKS_BORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];

class InfBlocks {
  Int32List hufts;
  Uint8List window;
  Inflate checkfn;
  int mode;
  int end;
  int left;
  int table;
  int index;
  var blens;
  Int32List bb;
  Int32List tb;
  InfCodes codes;
  int last;
  int bitk = 0; // bits in bit buffer
  int bitb = 0; // bit buffer
  int read = 0; // window read pointer
  int write = 0; // window write pointer
  int check = 0;
  InfTree inftree;

  InfBlocks(ZStream z, this.checkfn, int w) {
    this.hufts = new Int32List(MANY * 3);
    this.window = new Uint8List(w);
    this.end = w;
    this.checkfn = checkfn;
    this.mode = IB_TYPE;
    this.reset(z, null);

    this.left = 0; // if STORED, bytes left to copy

    this.table = 0; // table lengths (14 bits)
    this.index = 0; // index into blens (or border)
    this.blens = null; // bit lengths of codes
    this.bb = new Int32List(1); // bit length tree depth
    this.tb = new Int32List(1); // bit length decoding tree

    this.codes = InfCodes();

    this.last = 0; // true if this block is the last block

    // mode independent information
    this.bitk = 0; // bits in bit buffer
    this.bitb = 0; // bit buffer
    this.read = 0; // window read pointer
    this.write = 0; // window write pointer
    this.check = 0; // check on output

    this.inftree = InfTree();
  }

  reset(ZStream z, c) {
    if (c) c[0] = this.check;
    if (this.mode == IB_CODES) {
      this.codes.free(z);
    }
    this.mode = IB_TYPE;
    this.bitk = 0;
    this.bitb = 0;
    this.read = this.write = 0;

    if (this.checkfn != null) z.adler = this.check = adler32(0, null, 0, 0);
  }

  proc(ZStream z, r) {
    var t; // temporary storage
    var b; // bit buffer
    var k; // bits in bit buffer
    var p; // input data pointer
    var n; // bytes available there
    var q; // output window write pointer
    var m; // bytes to end of window or read pointer

    // copy input/output information to locals (UPDATE macro restores)
    {
      p = z.next_in_index;
      n = z.avail_in;
      b = this.bitb;
      k = this.bitk;
    }
    {
      q = this.write;
      m = (q < this.read ? this.read - q - 1 : this.end - q);
    }

    // process input based on current state
    while (true) {
      //console.log(this.mode);
      switch (this.mode) {
        case IB_TYPE:
          while (k < (3)) {
            if (n != 0) {
              r = Z_OK;
            } else {
              this.bitb = b;
              this.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              this.write = q;
              return this.inflate_flush(z, r);
            }
            ;
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }
          t = (b & 7);
          this.last = t & 1;

          switch (t >> 1) {
            case 0: // stored
              {
                b >>= (3);
                k -= (3);
              }
              t = k & 7; // go to byte boundary

              {
                b >>= (t);
                k -= (t);
              }
              this.mode = IB_LENS; // get length of stored block
              break;
            case 1: // fixed
              {
                var bl = new Int32List(1);
                var bd = new Int32List(1);
                var tl = [];
                var td = [];

                inflate_trees_fixed(bl, bd, tl, td, z);
                this.codes.init(bl[0], bd[0], tl[0], 0, td[0], 0, z);
              }

              {
                b >>= (3);
                k -= (3);
              }

              this.mode = IB_CODES;
              break;
            case 2: // dynamic

              {
                b >>= (3);
                k -= (3);
              }

              this.mode = IB_TABLE;
              break;
            case 3: // illegal

              {
                b >>= (3);
                k -= (3);
              }
              this.mode = BAD;
              z.msg = "invalid block type";
              r = Z_DATA_ERROR;

              this.bitb = b;
              this.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              this.write = q;
              return this.inflate_flush(z, r);
          }
          break;
        case IB_LENS:
          while (k < (32)) {
            if (n != 0) {
              r = Z_OK;
            } else {
              this.bitb = b;
              this.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              this.write = q;
              return this.inflate_flush(z, r);
            }
            ;
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) {
            this.mode = BAD;
            z.msg = "invalid stored block lengths";
            r = Z_DATA_ERROR;

            this.bitb = b;
            this.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            this.write = q;
            return this.inflate_flush(z, r);
          }
          this.left = (b & 0xffff);
          b = k = 0; // dump bits
          this.mode = this.left != 0 ? IB_STORED : (this.last != 0 ? IB_DRY : IB_TYPE);
          break;
        case IB_STORED:
          if (n == 0) {
            this.bitb = b;
            this.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            write = q;
            return this.inflate_flush(z, r);
          }

          if (m == 0) {
            if (q == this.end && this.read != 0) {
              q = 0;
              m = (q < this.read ? this.read - q - 1 : this.end - q);
            }
            if (m == 0) {
              this.write = q;
              r = this.inflate_flush(z, r);
              q = this.write;
              m = (q < this.read ? this.read - q - 1 : this.end - q);
              if (q == this.end && this.read != 0) {
                q = 0;
                m = (q < this.read ? this.read - q - 1 : this.end - q);
              }
              if (m == 0) {
                this.bitb = b;
                this.bitk = k;
                z.avail_in = n;
                z.total_in += p - z.next_in_index;
                z.next_in_index = p;
                this.write = q;
                return this.inflate_flush(z, r);
              }
            }
          }
          r = Z_OK;

          t = this.left;
          if (t > n) t = n;
          if (t > m) t = m; //console.log(window, this.window);
          arrayCopy(z.next_in, p, this.window, q, t);
          p += t;
          n -= t;
          q += t;
          m -= t;
          if ((this.left -= t) != 0) break;
          this.mode = (this.last != 0 ? IB_DRY : IB_TYPE);
          break;
        case IB_TABLE:
          while (k < (14)) {
            if (n != 0) {
              r = Z_OK;
            } else {
              this.bitb = b;
              this.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              this.write = q;
              return this.inflate_flush(z, r);
            }
            ;
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          this.table = t = (b & 0x3fff);
          if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) {
            this.mode = IB_BAD;
            z.msg = "too many length or distance symbols";
            r = Z_DATA_ERROR;

            this.bitb = b;
            this.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            this.write = q;
            return this.inflate_flush(z, r);
          }
          t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
          if (this.blens == null || this.blens.length < t) {
            this.blens = new Int32List(t);
          } else {
            for (var i = 0; i < t; i++) {
              this.blens[i] = 0;
            }
          }

          {
            b >>= (14);
            k -= (14);
          }

          this.index = 0;
          mode = IB_BTREE;
          continue;
        case IB_BTREE:
          while (this.index < 4 + (this.table >> 10)) {
            while (k < (3)) {
              if (n != 0) {
                r = Z_OK;
              } else {
                this.bitb = b;
                this.bitk = k;
                z.avail_in = n;
                z.total_in += p - z.next_in_index;
                z.next_in_index = p;
                this.write = q;
                return this.inflate_flush(z, r);
              }
              ;
              n--;
              b |= (z.next_in[p++] & 0xff) << k;
              k += 8;
            }

            this.blens[INFBLOCKS_BORDER[this.index++]] = b & 7;

            {
              b >>= (3);
              k -= (3);
            }
          }

          while (this.index < 19) {
            this.blens[INFBLOCKS_BORDER[this.index++]] = 0;
          }

          this.bb[0] = 7;
          t = this.inftree.inflate_trees_bits(this.blens, this.bb, this.tb, this.hufts, z);
          if (t != Z_OK) {
            r = t;
            if (r == Z_DATA_ERROR) {
              this.blens = null;
              this.mode = IB_BAD;
            }

            this.bitb = b;
            this.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            write = q;
            return this.inflate_flush(z, r);
          }

          this.index = 0;
          this.mode = IB_DTREE;
          continue;
        case IB_DTREE:
          while (true) {
            t = this.table;
            if (!(this.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f))) {
              break;
            }

            var h; //int[]
            var i, j, c;

            t = this.bb[0];

            while (k < (t)) {
              if (n != 0) {
                r = Z_OK;
              } else {
                this.bitb = b;
                this.bitk = k;
                z.avail_in = n;
                z.total_in += p - z.next_in_index;
                z.next_in_index = p;
                this.write = q;
                return this.inflate_flush(z, r);
              }
              ;
              n--;
              b |= (z.next_in[p++] & 0xff) << k;
              k += 8;
            }

//        if (this.tb[0]==-1){
//            dlog("null...");
//        }

            t = this.hufts[(this.tb[0] + (b & inflate_mask[t])) * 3 + 1];
            c = this.hufts[(this.tb[0] + (b & inflate_mask[t])) * 3 + 2];

            if (c < 16) {
              b >>= (t);
              k -= (t);
              this.blens[this.index++] = c;
            } else {
              // c == 16..18
              i = c == 18 ? 7 : c - 14;
              j = c == 18 ? 11 : 3;

              while (k < (t + i)) {
                if (n != 0) {
                  r = Z_OK;
                } else {
                  this.bitb = b;
                  this.bitk = k;
                  z.avail_in = n;
                  z.total_in += p - z.next_in_index;
                  z.next_in_index = p;
                  this.write = q;
                  return this.inflate_flush(z, r);
                }
                ;
                n--;
                b |= (z.next_in[p++] & 0xff) << k;
                k += 8;
              }

              b >>= (t);
              k -= (t);

              j += (b & inflate_mask[i]);

              b >>= (i);
              k -= (i);

              i = this.index;
              t = this.table;
              if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) {
                this.blens = null;
                this.mode = IB_BAD;
                z.msg = "invalid bit length repeat";
                r = Z_DATA_ERROR;

                this.bitb = b;
                this.bitk = k;
                z.avail_in = n;
                z.total_in += p - z.next_in_index;
                z.next_in_index = p;
                this.write = q;
                return this.inflate_flush(z, r);
              }

              c = c == 16 ? this.blens[i - 1] : 0;
              do {
                this.blens[i++] = c;
              } while (--j != 0);
              this.index = i;
            }
          }

          this.tb[0] = -1;
          {
            var bl = new Int32List(1);
            var bd = new Int32List(1);
            var tl = new Int32List(1);
            var td = new Int32List(1);
            bl[0] = 9; // must be <= 9 for lookahead assumptions
            bd[0] = 6; // must be <= 9 for lookahead assumptions

            t = this.table;
            t = this.inftree.inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), this.blens, bl, bd, tl, td, this.hufts, z);

            if (t != Z_OK) {
              if (t == Z_DATA_ERROR) {
                this.blens = null;
                this.mode = BAD;
              }
              r = t;

              this.bitb = b;
              this.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              this.write = q;
              return this.inflate_flush(z, r);
            }
            this.codes.init(bl[0], bd[0], this.hufts, tl[0], this.hufts, td[0], z);
          }
          this.mode = IB_CODES;
          continue;
        case IB_CODES:
          this.bitb = b;
          this.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          this.write = q;

          if ((r = this.codes.proc(this, z, r)) != Z_STREAM_END) {
            return this.inflate_flush(z, r);
          }
          r = Z_OK;
          this.codes.free(z);

          p = z.next_in_index;
          n = z.avail_in;
          b = this.bitb;
          k = this.bitk;
          q = this.write;
          m = (q < this.read ? this.read - q - 1 : this.end - q);

          if (this.last == 0) {
            this.mode = IB_TYPE;
            break;
          }
          this.mode = IB_DRY;
          continue;
        case IB_DRY:
          this.write = q;
          r = this.inflate_flush(z, r);
          q = this.write;
          m = (q < this.read ? this.read - q - 1 : this.end - q);
          if (this.read != this.write) {
            this.bitb = b;
            this.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            this.write = q;
            return this.inflate_flush(z, r);
          }
          mode = DONE;
          continue;
        case IB_DONE:
          r = Z_STREAM_END;

          this.bitb = b;
          this.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          this.write = q;
          return this.inflate_flush(z, r);
        case IB_BAD:
          r = Z_DATA_ERROR;

          this.bitb = b;
          this.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          this.write = q;
          return this.inflate_flush(z, r);

        default:
          r = Z_STREAM_ERROR;

          this.bitb = b;
          this.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          this.write = q;
          return this.inflate_flush(z, r);
      }
    }
  }

  free(ZStream z) {
    this.reset(z, null);
    this.window = null;
    this.hufts = null;
  }

  set_dictionary(d, start, n) {
    arrayCopy(d, start, this.window, 0, n);
    this.read = this.write = n;
  }

// Returns true if inflate is currently at the end of a block generated
// by Z_SYNC_FLUSH or Z_FULL_FLUSH.
  sync_point() {
    return this.mode == IB_LENS;
  }

// copy as much as possible from the sliding window to the output area
  inflate_flush(ZStream z, r) {
    var n;
    var p;
    int q;

    // local copies of source and destination pointers
    p = z.next_out_index;
    q = this.read;

    // compute number of bytes to copy as far as end of window
    n = ((q <= this.write ? this.write : this.end) - q);
    if (n > z.avail_out) n = z.avail_out;
    if (n != 0 && r == Z_BUF_ERROR) r = Z_OK;

    // update counters
    z.avail_out -= n;
    z.total_out += n;

    // update check information
    if (this.checkfn != null) z.adler = this.check = adler32(this.check, this.window, q, n);

    // copy as far as end of window
    arrayCopy(this.window, q, z.next_out, p, n);
    p += n;
    q += n;

    // see if more to copy at beginning of window
    if (q == this.end) {
      // wrap pointers
      q = 0;
      if (this.write == this.end) this.write = 0;

      // compute bytes to copy
      n = this.write - q;
      if (n > z.avail_out) n = z.avail_out;
      if (n != 0 && r == Z_BUF_ERROR) r = Z_OK;

      // update counters
      z.avail_out -= n;
      z.total_out += n;

      // update check information
      if (this.checkfn != null) z.adler = this.check = adler32(this.check, this.window, q, n);

      // copy
      arrayCopy(this.window, q, z.next_out, p, n);
      p += n;
      q += n;
    }

    // update pointers
    z.next_out_index = p;
    this.read = q;

    // done
    return r;
  }
}

//
// InfCodes.java
//

const IC_START = 0; // x: set up for LEN
const IC_LEN = 1; // i: get length/literal/eob next
const IC_LENEXT = 2; // i: getting length extra (have base)
const IC_DIST = 3; // i: get distance next
const IC_DISTEXT = 4; // i: getting distance extra
const IC_COPY = 5; // o: copying bytes in window, waiting for space
const IC_LIT = 6; // o: got literal, waiting for output space
const IC_WASH = 7; // o: got eob, possibly still output waiting
const IC_END = 8; // x: got eob and all data flushed
const IC_BADCODE = 9; // x: got error

class InfCodes {
  int mode;
  var lbits;
  var dbits;
  var ltree;
  var ltree_index;
  var dtree;
  var dtree_index;
  var tree;
  var need;

  var tree_index;
  var lit;

  var get;
  var len;

  var dist;

  init(bl, bd, tl, tl_index, td, td_index, z) {
    this.mode = IC_START;
    this.lbits = bl;
    this.dbits = bd;
    this.ltree = tl;
    this.ltree_index = tl_index;
    this.dtree = td;
    this.dtree_index = td_index;
    this.tree = null;
  }

  proc(InfBlocks s, ZStream z, r) {
    var j; // temporary storage
    var t; // temporary pointer (int[])
    var tindex; // temporary pointer
    var e; // extra bits or operation
    var b = 0; // bit buffer
    var k = 0; // bits in bit buffer
    var p = 0; // input data pointer
    var n; // bytes available there
    var q; // output window write pointer
    var m; // bytes to end of window or read pointer
    var f; // pointer to copy strings from

    // copy input/output information to locals (UPDATE macro restores)
    p = z.next_in_index;
    n = z.avail_in;
    b = s.bitb;
    k = s.bitk;
    q = s.write;
    m = q < s.read ? s.read - q - 1 : s.end - q;

    // process input and output based on current state
    while (true) {
      switch (this.mode) {
        // waiting for "i:"=input, "o:"=output, "x:"=nothing
        case IC_START: // x: set up for LEN
          if (m >= 258 && n >= 10) {
            s.bitb = b;
            s.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            s.write = q;
            r = this.inflate_fast(this.lbits, this.dbits, this.ltree, this.ltree_index, this.dtree, this.dtree_index, s, z);

            p = z.next_in_index;
            n = z.avail_in;
            b = s.bitb;
            k = s.bitk;
            q = s.write;
            m = q < s.read ? s.read - q - 1 : s.end - q;

            if (r != Z_OK) {
              this.mode = r == Z_STREAM_END ? IC_WASH : IC_BADCODE;
              break;
            }
          }
          this.need = this.lbits;
          this.tree = this.ltree;
          this.tree_index = this.ltree_index;

          this.mode = IC_LEN;
          continue;
        case IC_LEN: // i: get length/literal/eob next
          j = this.need;

          while (k < (j)) {
            if (n != 0)
              r = Z_OK;
            else {
              s.bitb = b;
              s.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              s.write = q;
              return s.inflate_flush(z, r);
            }
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          tindex = (this.tree_index + (b & inflate_mask[j])) * 3;

          b >>= (this.tree[tindex + 1]);
          k -= (this.tree[tindex + 1]);

          e = this.tree[tindex];

          if (e == 0) {
            // literal
            this.lit = this.tree[tindex + 2];
            this.mode = IC_LIT;
            break;
          }
          if ((e & 16) != 0) {
            // length
            this.get = e & 15;
            this.len = this.tree[tindex + 2];
            this.mode = IC_LENEXT;
            break;
          }
          if ((e & 64) == 0) {
            // next table
            this.need = e;
            this.tree_index = tindex / 3 + this.tree[tindex + 2];
            break;
          }
          if ((e & 32) != 0) {
            // end of block
            this.mode = IC_WASH;
            break;
          }
          this.mode = IC_BADCODE; // invalid code
          z.msg = "invalid literal/length code";
          r = Z_DATA_ERROR;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;
          return s.inflate_flush(z, r);

        case IC_LENEXT: // i: getting length extra (have base)
          j = this.get;

          while (k < (j)) {
            if (n != 0)
              r = Z_OK;
            else {
              s.bitb = b;
              s.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              s.write = q;
              return s.inflate_flush(z, r);
            }
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          this.len += (b & inflate_mask[j]);

          b >>= j;
          k -= j;

          this.need = this.dbits;
          this.tree = this.dtree;
          this.tree_index = this.dtree_index;
          this.mode = IC_DIST;
          continue;
        case IC_DIST: // i: get distance next
          j = this.need;

          while (k < (j)) {
            if (n != 0)
              r = Z_OK;
            else {
              s.bitb = b;
              s.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              s.write = q;
              return s.inflate_flush(z, r);
            }
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          tindex = (this.tree_index + (b & inflate_mask[j])) * 3;

          b >>= this.tree[tindex + 1];
          k -= this.tree[tindex + 1];

          e = (this.tree[tindex]);
          if ((e & 16) != 0) {
            // distance
            this.get = e & 15;
            this.dist = this.tree[tindex + 2];
            this.mode = IC_DISTEXT;
            break;
          }
          if ((e & 64) == 0) {
            // next table
            this.need = e;
            this.tree_index = tindex / 3 + this.tree[tindex + 2];
            break;
          }
          this.mode = IC_BADCODE; // invalid code
          z.msg = "invalid distance code";
          r = Z_DATA_ERROR;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;
          return s.inflate_flush(z, r);

        case IC_DISTEXT: // i: getting distance extra
          j = this.get;

          while (k < (j)) {
            if (n != 0)
              r = Z_OK;
            else {
              s.bitb = b;
              s.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              s.write = q;
              return s.inflate_flush(z, r);
            }
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          this.dist += (b & inflate_mask[j]);

          b >>= j;
          k -= j;

          this.mode = IC_COPY;
          continue;
        case IC_COPY: // o: copying bytes in window, waiting for space
          f = q - this.dist;
          while (f < 0) {
            // modulo window size-"while" instead
            f += s.end; // of "if" handles invalid distances
          }
          while (this.len != 0) {
            if (m == 0) {
              if (q == s.end && s.read != 0) {
                q = 0;
                m = q < s.read ? s.read - q - 1 : s.end - q;
              }
              if (m == 0) {
                s.write = q;
                r = s.inflate_flush(z, r);
                q = s.write;
                m = q < s.read ? s.read - q - 1 : s.end - q;

                if (q == s.end && s.read != 0) {
                  q = 0;
                  m = q < s.read ? s.read - q - 1 : s.end - q;
                }

                if (m == 0) {
                  s.bitb = b;
                  s.bitk = k;
                  z.avail_in = n;
                  z.total_in += p - z.next_in_index;
                  z.next_in_index = p;
                  s.write = q;
                  return s.inflate_flush(z, r);
                }
              }
            }

            s.window[q++] = s.window[f++];
            m--;

            if (f == s.end) f = 0;
            this.len--;
          }
          this.mode = IC_START;
          break;
        case IC_LIT: // o: got literal, waiting for output space
          if (m == 0) {
            if (q == s.end && s.read != 0) {
              q = 0;
              m = q < s.read ? s.read - q - 1 : s.end - q;
            }
            if (m == 0) {
              s.write = q;
              r = s.inflate_flush(z, r);
              q = s.write;
              m = q < s.read ? s.read - q - 1 : s.end - q;

              if (q == s.end && s.read != 0) {
                q = 0;
                m = q < s.read ? s.read - q - 1 : s.end - q;
              }
              if (m == 0) {
                s.bitb = b;
                s.bitk = k;
                z.avail_in = n;
                z.total_in += p - z.next_in_index;
                z.next_in_index = p;
                s.write = q;
                return s.inflate_flush(z, r);
              }
            }
          }
          r = Z_OK;

          s.window[q++] = this.lit;
          m--;

          this.mode = IC_START;
          break;
        case IC_WASH: // o: got eob, possibly more output
          if (k > 7) {
            // return unused byte, if any
            k -= 8;
            n++;
            p--; // can always return one
          }

          s.write = q;
          r = s.inflate_flush(z, r);
          q = s.write;
          m = q < s.read ? s.read - q - 1 : s.end - q;

          if (s.read != s.write) {
            s.bitb = b;
            s.bitk = k;
            z.avail_in = n;
            z.total_in += p - z.next_in_index;
            z.next_in_index = p;
            s.write = q;
            return s.inflate_flush(z, r);
          }
          this.mode = IC_END;
          continue;
        case IC_END:
          r = Z_STREAM_END;
          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;
          return s.inflate_flush(z, r);

        case IC_BADCODE: // x: got error

          r = Z_DATA_ERROR;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;
          return s.inflate_flush(z, r);

        default:
          r = Z_STREAM_ERROR;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;
          return s.inflate_flush(z, r);
      }
    }
  }

  free(z) {
    //  ZFREE(z, c);
  }

// Called with number of bytes left to write in window at least 258
// (the maximum string length) and number of input bytes available
// at least ten.  The ten bytes are six bytes for the longest length/
// distance pair plus four bytes for overloading the bit buffer.

  inflate_fast(bl, bd, tl, tl_index, td, td_index, InfBlocks s, ZStream z) {
    var t; // temporary pointer
    var tp; // temporary pointer (int[])
    var tp_index; // temporary pointer
    var e; // extra bits or operation
    var b; // bit buffer
    var k; // bits in bit buffer
    var p; // input data pointer
    var n; // bytes available there
    var q; // output window write pointer
    var m; // bytes to end of window or read pointer
    var ml; // mask for literal/length tree
    var md; // mask for distance tree
    var c; // bytes to copy
    var d; // distance back to copy from
    var r; // copy source pointer

    var tp_index_t_3; // (tp_index+t)*3

    // load input, output, bit values
    p = z.next_in_index;
    n = z.avail_in;
    b = s.bitb;
    k = s.bitk;
    q = s.write;
    m = q < s.read ? s.read - q - 1 : s.end - q;

    // initialize masks
    ml = inflate_mask[bl];
    md = inflate_mask[bd];

    // do until not enough input or output space for fast loop
    do {
      // assume called with m >= 258 && n >= 10
      // get literal/length code
      while (k < (20)) {
        // max bits for literal/length code
        n--;
        b |= (z.next_in[p++] & 0xff) << k;
        k += 8;
      }

      t = b & ml;
      tp = tl;
      tp_index = tl_index;
      tp_index_t_3 = (tp_index + t) * 3;
      if ((e = tp[tp_index_t_3]) == 0) {
        b >>= (tp[tp_index_t_3 + 1]);
        k -= (tp[tp_index_t_3 + 1]);

        s.window[q++] = tp[tp_index_t_3 + 2];
        m--;
        continue;
      }
      do {
        b >>= (tp[tp_index_t_3 + 1]);
        k -= (tp[tp_index_t_3 + 1]);

        if ((e & 16) != 0) {
          e &= 15;
          c = tp[tp_index_t_3 + 2] + (b & inflate_mask[e]);

          b >>= e;
          k -= e;

          // decode distance base of block to copy
          while (k < (15)) {
            // max bits for distance code
            n--;
            b |= (z.next_in[p++] & 0xff) << k;
            k += 8;
          }

          t = b & md;
          tp = td;
          tp_index = td_index;
          tp_index_t_3 = (tp_index + t) * 3;
          e = tp[tp_index_t_3];

          do {
            b >>= (tp[tp_index_t_3 + 1]);
            k -= (tp[tp_index_t_3 + 1]);

            if ((e & 16) != 0) {
              // get extra bits to add to distance base
              e &= 15;
              while (k < (e)) {
                // get extra bits (up to 13)
                n--;
                b |= (z.next_in[p++] & 0xff) << k;
                k += 8;
              }

              d = tp[tp_index_t_3 + 2] + (b & inflate_mask[e]);

              b >>= (e);
              k -= (e);

              // do the copy
              m -= c;
              if (q >= d) {
                // offset before dest
                //  just copy
                r = q - d;
                if (q - r > 0 && 2 > (q - r)) {
                  s.window[q++] = s.window[r++]; // minimum count is three,
                  s.window[q++] = s.window[r++]; // so unroll loop a little
                  c -= 2;
                } else {
                  s.window[q++] = s.window[r++]; // minimum count is three,
                  s.window[q++] = s.window[r++]; // so unroll loop a little
                  c -= 2;
                }
              } else {
                // else offset after destination
                r = q - d;
                do {
                  r += s.end; // force pointer in window
                } while (r < 0); // covers invalid distances
                e = s.end - r;
                if (c > e) {
                  // if source crosses,
                  c -= e; // wrapped copy
                  if (q - r > 0 && e > (q - r)) {
                    do {
                      s.window[q++] = s.window[r++];
                    } while (--e != 0);
                  } else {
                    arrayCopy(s.window, r, s.window, q, e);
                    q += e;
                    r += e;
                    e = 0;
                  }
                  r = 0; // copy rest from start of window
                }
              }

              // copy all or what's left
              do {
                s.window[q++] = s.window[r++];
              } while (--c != 0);
              break;
            } else if ((e & 64) == 0) {
              t += tp[tp_index_t_3 + 2];
              t += (b & inflate_mask[e]);
              tp_index_t_3 = (tp_index + t) * 3;
              e = tp[tp_index_t_3];
            } else {
              z.msg = "invalid distance code";

              c = z.avail_in - n;
              c = (k >> 3) < c ? k >> 3 : c;
              n += c;
              p -= c;
              k -= c << 3;

              s.bitb = b;
              s.bitk = k;
              z.avail_in = n;
              z.total_in += p - z.next_in_index;
              z.next_in_index = p;
              s.write = q;

              return Z_DATA_ERROR;
            }
          } while (true);
          break;
        }

        if ((e & 64) == 0) {
          t += tp[tp_index_t_3 + 2];
          t += (b & inflate_mask[e]);
          tp_index_t_3 = (tp_index + t) * 3;
          if ((e = tp[tp_index_t_3]) == 0) {
            b >>= (tp[tp_index_t_3 + 1]);
            k -= (tp[tp_index_t_3 + 1]);

            s.window[q++] = tp[tp_index_t_3 + 2];
            m--;
            break;
          }
        } else if ((e & 32) != 0) {
          c = z.avail_in - n;
          c = (k >> 3) < c ? k >> 3 : c;
          n += c;
          p -= c;
          k -= c << 3;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;

          return Z_STREAM_END;
        } else {
          z.msg = "invalid literal/length code";

          c = z.avail_in - n;
          c = (k >> 3) < c ? k >> 3 : c;
          n += c;
          p -= c;
          k -= c << 3;

          s.bitb = b;
          s.bitk = k;
          z.avail_in = n;
          z.total_in += p - z.next_in_index;
          z.next_in_index = p;
          s.write = q;

          return Z_DATA_ERROR;
        }
      } while (true);
    } while (m >= 258 && n >= 10);

    // not enough input or output--restore pointers and return
    c = z.avail_in - n;
    c = (k >> 3) < c ? k >> 3 : c;
    n += c;
    p -= c;
    k -= c << 3;

    s.bitb = b;
    s.bitk = k;
    z.avail_in = n;
    z.total_in += p - z.next_in_index;
    z.next_in_index = p;
    s.write = q;

    return Z_OK;
  }
}

//
// InfTree.java
//

class InfTree {
  List c;
  List x;
  List v;
  List u;
  List hn;
  List r;

  huft_build(b, bindex, n, s, d, e, t, m, hp, hn, v) {
    // Given a list of code lengths and a maximum table size, make a set of
    // tables to decode that set of codes.  Return Z_OK on success, Z_BUF_ERROR
    // if the given code set is incomplete (the tables are still built in this
    // case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
    // lengths), or Z_MEM_ERROR if not enough memory.

    var a; // counter for codes of length k
    var f; // i repeats in table every f entries
    var g; // maximum code length
    var h; // table level
    var i; // counter, current code
    var j; // counter
    var k; // number of bits in current code
    var l; // bits per table (returned in m)
    var mask; // (1 << w) - 1, to avoid cc -O bug on HP
    var p; // pointer into c[], b[], or v[]
    var q; // points to current table
    var w; // bits before this table == (l * h)
    var xp; // pointer into x
    var y; // number of dummy codes added
    var z; // number of entries in current table

    // Generate counts for each bit length

    p = 0;
    i = n;
    do {
      this.c[b[bindex + p]]++;
      p++;
      i--; // assume all entries <= BMAX
    } while (i != 0);

    if (this.c[0] == n) {
      // null input--all zero length codes
      t[0] = -1;
      m[0] = 0;
      return Z_OK;
    }

    // Find minimum and maximum length, bound *m by those
    l = m[0];
    for (j = 1; j <= BMAX; j++) if (this.c[j] != 0) break;
    k = j; // minimum code length
    if (l < j) {
      l = j;
    }
    for (i = BMAX; i != 0; i--) {
      if (this.c[i] != 0) break;
    }
    g = i; // maximum code length
    if (l > i) {
      l = i;
    }
    m[0] = l;

    // Adjust last length count to fill out codes, if needed
    for (y = 1 << j; j < i; j++, y <<= 1) {
      if ((y -= this.c[j]) < 0) {
        return Z_DATA_ERROR;
      }
    }
    if ((y -= this.c[i]) < 0) {
      return Z_DATA_ERROR;
    }
    this.c[i] += y;

    // Generate starting offsets into the value table for each length
    this.x[1] = j = 0;
    p = 1;
    xp = 2;
    while (--i != 0) {
      // note that i == g from above
      this.x[xp] = (j += this.c[p]);
      xp++;
      p++;
    }

    // Make a table of values in order of bit lengths
    i = 0;
    p = 0;
    do {
      if ((j = b[bindex + p]) != 0) {
        this.v[this.x[j]++] = i;
      }
      p++;
    } while (++i < n);
    n = this.x[g]; // set n to length of v

    // Generate the Huffman codes and for each, make the table entries
    this.x[0] = i = 0; // first Huffman code is zero
    p = 0; // grab values in bit order
    h = -1; // no tables yet--level -1
    w = -l; // bits decoded == (l * h)
    this.u[0] = 0; // just to keep compilers happy
    q = 0; // ditto
    z = 0; // ditto

    // go through the bit lengths (k already is bits in shortest code)
    for (; k <= g; k++) {
      a = this.c[k];
      while (a-- != 0) {
        // here i is the Huffman code of length k bits for value *p
        // make tables up to required level
        while (k > w + l) {
          h++;
          w += l; // previous table always l bits
          // compute minimum size table less than or equal to l bits
          z = g - w;
          z = (z > l) ? l : z; // table size upper limit
          if ((f = 1 << (j = k - w)) > a + 1) {
            // try a k-w bit table
            // too few codes for k-w bit table
            f -= a + 1; // deduct codes from patterns left
            xp = k;
            if (j < z) {
              while (++j < z) {
                // try smaller tables up to z bits
                if ((f <<= 1) <= this.c[++xp]) break; // enough codes to use up j bits
                f -= this.c[xp]; // else deduct codes from patterns
              }
            }
          }
          z = 1 << j; // table entries for j-bit table

          // allocate new table
          if (this.hn[0] + z > MANY) {
            // (note: doesn't matter for fixed)
            return Z_DATA_ERROR; // overflow of MANY
          }
          this.u[h] = q = /*hp+*/ this.hn[0]; // DEBUG
          this.hn[0] += z;

          // connect to last table, if there is one
          if (h != 0) {
            this.x[h] = i; // save pattern for backing up
            this.r[0] = j; // bits in this table
            this.r[1] = l; // bits to dump before this table
            j = i >> (w - l);
            this.r[2] = (q - this.u[h - 1] - j); // offset to this table
            arrayCopy(this.r, 0, hp, (this.u[h - 1] + j) * 3, 3); // connect to last table
          } else {
            t[0] = q; // first table is returned result
          }
        }

        // set up table entry in r
        this.r[1] = (k - w);
        if (p >= n) {
          this.r[0] = 128 + 64; // out of values--invalid code
        } else if (v[p] < s) {
          this.r[0] = (this.v[p] < 256 ? 0 : 32 + 64); // 256 is end-of-block
          this.r[2] = this.v[p++]; // simple code is just the value
        } else {
          this.r[0] = (e[this.v[p] - s] + 16 + 64); // non-simple--look up in lists
          this.r[2] = d[this.v[p++] - s];
        }

        // fill code-like entries with r
        f = 1 << (k - w);
        for (j = i >> w; j < z; j += f) {
          arrayCopy(this.r, 0, hp, (q + j) * 3, 3);
        }

        // backwards increment the k-bit code i
        for (j = 1 << (k - 1); (i & j) != 0; j >>= 1) {
          i ^= j;
        }
        i ^= j;

        // backup over finished tables
        mask = (1 << w) - 1; // needed on HP, cc -O bug
        while ((i & mask) != this.x[h]) {
          h--; // don't need to update q
          w -= l;
          mask = (1 << w) - 1;
        }
      }
    }
    // Return Z_BUF_ERROR if we were given an incomplete table
    return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK;
  }

  inflate_trees_bits(c, bb, tb, hp, z) {
    var result;
    this.initWorkArea(19);
    this.hn[0] = 0;
    result = this.huft_build(c, 0, 19, 19, null, null, tb, bb, hp, this.hn, this.v);

    if (result == Z_DATA_ERROR) {
      z.msg = "oversubscribed dynamic bit lengths tree";
    } else if (result == Z_BUF_ERROR || bb[0] == 0) {
      z.msg = "incomplete dynamic bit lengths tree";
      result = Z_DATA_ERROR;
    }
    return result;
  }

  inflate_trees_dynamic(nl, nd, c, bl, bd, tl, td, hp, z) {
    var result;

    // build literal/length tree
    this.initWorkArea(288);
    this.hn[0] = 0;
    result = this.huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, this.hn, this.v);
    if (result != Z_OK || bl[0] == 0) {
      if (result == Z_DATA_ERROR) {
        z.msg = "oversubscribed literal/length tree";
      } else if (result != Z_MEM_ERROR) {
        z.msg = "incomplete literal/length tree";
        result = Z_DATA_ERROR;
      }
      return result;
    }

    // build distance tree
    this.initWorkArea(288);
    result = this.huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, this.hn, this.v);

    if (result != Z_OK || (bd[0] == 0 && nl > 257)) {
      if (result == Z_DATA_ERROR) {
        z.msg = "oversubscribed distance tree";
      } else if (result == Z_BUF_ERROR) {
        z.msg = "incomplete distance tree";
        result = Z_DATA_ERROR;
      } else if (result != Z_MEM_ERROR) {
        z.msg = "empty distance tree with lengths";
        result = Z_DATA_ERROR;
      }
      return result;
    }

    return Z_OK;
  }

  initWorkArea(int vsize) {
    if (this.hn == null) {
      this.hn = Int32List(1);
      this.v = Int32List(vsize);
      this.c = Int32List(BMAX + 1);
      this.r = Int32List(3);
      this.u = Int32List(BMAX);
      this.x = Int32List(BMAX + 1);
    }
    if (this.v.length < vsize) {
      this.v = Int32List(vsize);
    }
    for (var i = 0; i < vsize; i++) {
      this.v[i] = 0;
    }
    for (var i = 0; i < BMAX + 1; i++) {
      this.c[i] = 0;
    }
    for (var i = 0; i < 3; i++) {
      this.r[i] = 0;
    }
//  for(int i=0; i<BMAX; i++){u[i]=0;}
    arrayCopy(this.c, 0, this.u, 0, BMAX);
//  for(int i=0; i<BMAX+1; i++){x[i]=0;}
    arrayCopy(this.c, 0, this.x, 0, BMAX + 1);
  }
}

/*
  static int inflate_trees_fixed(int[] bl,  //literal desired/actual bit depth
                                 int[] bd,  //distance desired/actual bit depth
                                 int[][] tl,//literal/length tree result
                                 int[][] td,//distance tree result
                                 ZStream z  //for memory allocation
                                 ){
*/

inflate_trees_fixed(bl, bd, tl, td, z) {
  bl[0] = fixed_bl;
  bd[0] = fixed_bd;
  tl[0] = fixed_tl;
  td[0] = fixed_td;
  return Z_OK;
}

var testArray = new Uint8List(1);
var hasSubarray = true; //(typeof testArray.subarray === 'function');
var hasSlice = false; /* (typeof testArray.slice === 'function'); */ // Chrome slice performance is so dire that we're currently not using it...

arrayCopy(Uint8List src, srcOffset, Uint8List dest, destOffset, int count) {
  if (count == 0) {
    return;
  }
  if (src == null) {
    throw "Undef src";
  } else if (dest == null) {
    throw "Undef dest";
  }

  var jsaDbg = src.sublist(srcOffset, srcOffset + count);
  var jsaDest = dest;
  var jsaDestoffset = destOffset;

  if (srcOffset == 0 && count == src.length) {
    arrayCopy_fast(src, dest, destOffset);
  } else if (hasSubarray) {
    arrayCopy_fast(src.sublist(srcOffset, srcOffset + count), dest, destOffset);
  }
  // else if (src.BYTES_PER_ELEMENT == 1 && count > 100) {
  //   arrayCopy_fast(Uint8List.view(src, src.byteOffset + srcOffset, count), dest, destOffset);
  // }
  else {
    arrayCopy_slow(src, srcOffset, dest, destOffset, count);
  }
}

arrayCopy_slow(src, srcOffset, dest, destOffset, count) {
  // dlog('_slow call: srcOffset=' + srcOffset + '; destOffset=' + destOffset + '; count=' + count);

  for (var i = 0; i < count; ++i) {
    dest[destOffset + i] = src[srcOffset + i];
  }
}

arrayCopy_fast(List src, List dest, destOffset) {
  dest.setAll(destOffset, src);
}

// largest prime smaller than 65536
var ADLER_BASE = 65521;
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
var ADLER_NMAX = 5552;

adler32(adler, /* byte[] */ buf, index, len) {
  if (buf == null) {
    return 1;
  }

  var s1 = adler & 0xffff;
  var s2 = (adler >> 16) & 0xffff;
  var k;

  while (len > 0) {
    k = len < ADLER_NMAX ? len : ADLER_NMAX;
    len -= k;
    while (k >= 16) {
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      s1 += buf[index++] & 0xff;
      s2 += s1;
      k -= 16;
    }
    if (k != 0) {
      do {
        s1 += buf[index++] & 0xff;
        s2 += s1;
      } while (--k != 0);
    }
    s1 %= ADLER_BASE;
    s2 %= ADLER_BASE;
  }
  return (s2 << 16) | s1;
}

jszlib_inflate_buffer(Uint8List buffer, start, length, afterUncOffset) {
  //console.log(start, length);
  if (start == null) {
    buffer = new Uint8List.fromList(buffer);
  } else {
    buffer = new Uint8List.sublistView(buffer, start, length);
  }

  var z = new ZStream();
  z.inflateInit(DEF_WBITS, true);
  z.next_in = buffer;
  z.next_in_index = 0;
  z.avail_in = buffer.length;

  List<Uint8List> oBlockList = [];
  var totalSize = 0;
  while (true) {
    var obuf = Uint8List(32000);
    z.next_out = obuf;
    z.next_out_index = 0;
    z.avail_out = obuf.length;
    var status = z.inflate(Z_NO_FLUSH);
    if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) {
      throw z.msg;
    }
    if (z.avail_out != 0) {
      var newob = Uint8List(obuf.length - z.avail_out);
      arrayCopy(obuf, 0, newob, 0, (obuf.length - z.avail_out));
      obuf = newob;
    }
    oBlockList.add(obuf);
    totalSize += obuf.length;
    if (status == Z_STREAM_END || status == Z_BUF_ERROR) {
      break;
    }
  }

  if (afterUncOffset) {
    afterUncOffset[0] = (start ?? 0) + z.next_in_index;
  }

  if (oBlockList.length == 1) {
    return oBlockList[0].buffer;
  } else {
    var out = Uint8List(totalSize);
    var cursor = 0;
    for (var i = 0; i < oBlockList.length; ++i) {
      var b = oBlockList[i];
      arrayCopy(b, 0, out, cursor, b.length);
      cursor += b.length;
    }
    return out.buffer;
  }
}
